package de.core.gameplay.actor;

import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import de.core.gameplay.Updateable;
import de.core.gameplay.level.Level;
import de.core.graphics.Renderable;


public abstract class AbstractActor implements Renderable, Updateable {

    private BufferedImage image;
    private Point2D location;
    private int rotationAngle;

    private Level level;


    public AbstractActor(BufferedImage aImage) {
        this(aImage, 0, 0);
    }

    public AbstractActor(BufferedImage aImage, int aX, int aY) {
        image = aImage;
        location = new Point2D.Double(aX, aY);
    }

    public void setLevel(Level aLevel) {
        level = aLevel;
    }

    protected Level getLevel() {
        return level;
    }

    public final BufferedImage getImage() {
        return image;
    }

    public final void setImage(BufferedImage aImage) {
        image = aImage;
    }

    public final Point2D getLocation() {
        return location;
    }

    public void setLocation(int aX, int aY) {
        setLocation(new Point2D.Double(aX, aY));
    }

    public void setLocation(Point2D aPoint) {
        location = aPoint;
    }

    public void rotate(int aAngle) {
        rotationAngle += aAngle;
        while (rotationAngle >= 360) {
            rotationAngle -= 360;
        }
        while (rotationAngle < 0) {
            rotationAngle += 360;
        }
    }

    public final int getRotation() {
        return rotationAngle;
    }

    @Override
    public void render(Graphics2D aRenderGraphics, float aInterpolation) {
        if (getImage() == null) {
            throw new IllegalStateException("Cannot render Actor without Image!");
        }
        Point2D imageLocation = getImageRenderLocation(aInterpolation);

        AffineTransform transformation = new AffineTransform();
        transformation.setToTranslation(imageLocation.getX(), imageLocation.getY());
        transformation.rotate(Math.toRadians(getRotation()), getImage().getWidth() / 2, getImage().getHeight() / 2);

        aRenderGraphics.drawImage(getImage(), transformation, null);
    }

    protected Point2D getImageRenderLocation(float aInterpolation) {
        double centerX = getLocation().getX() - getImage().getWidth(null) / 2;
        double centerY = getLocation().getY() - getImage().getHeight(null) / 2;
        return new Point2D.Double(centerX, centerY);
    }

    @Override
    public void updateGameState() {
        act();
    }

    protected abstract void act();

    protected final List<AbstractActor> getActorsInRange(double aRange) {
        List<AbstractActor> actorsInWorld = getLevel().getActors();
        List<AbstractActor> actorsInRange = new ArrayList<AbstractActor>();
        for (AbstractActor actor : actorsInWorld) {
            if (getLocation().distance(actor.getLocation()) <= aRange) {
                actorsInRange.add(actor);
            }
        }
        return actorsInRange;
    }


    /**
     * Determines whether or not this <code>Actor</code> and the specified
     * <code>Actor</code> intersect. Two Actors intersect if
     * their images intersect.
     */
    public boolean intersects(AbstractActor aActor) {
        int ownWidth = getImage().getWidth();
        int ownHeight = getImage().getHeight();
        int othersWidth = aActor.getImage().getWidth();
        int othersHeight = aActor.getImage().getHeight();
        if (othersWidth <= 0 || othersHeight <= 0
            || ownWidth <= 0 || ownHeight <= 0) {
            return false;
        }

        othersWidth += aActor.getLocation().getX();
        othersHeight += aActor.getLocation().getY();
        ownWidth += getLocation().getX();
        ownHeight += getLocation().getY();

        return (othersWidth < aActor.getLocation().getX() || othersWidth > getLocation().getX())
            && (othersHeight < aActor.getLocation().getY() || othersHeight > getLocation().getY())
            && (ownWidth < getLocation().getX() || ownWidth > aActor.getLocation().getX())
            && (ownHeight < getLocation().getY() || ownHeight > aActor.getLocation().getY());
    }

    protected void removeFromLevel() {
        getLevel().removeActor(this);
    }
}
